#include <dirent.h>
#include <iostream>
#include <fstream>
#include <map>
#include <string>
#include <sstream>
#include <vector>

#include <boost/tokenizer.hpp>
#include <boost/shared_ptr.hpp>

#define DMCS    "dmcs"
#define DMCSOPT "dmcsopt"
#define DAT     ".dat"

#define DIAMOND "diamond"
#define ZIGZAG  "zig"
#define RING    "ring"
#define HOUSE   "house"
#define TREE    "tree"

#define DIAMOND_TOPO 1
#define ZIGZAG_TOPO  2
#define RING_TOPO    3
#define HOUSE_TOPO   4
#define TREE_TOPO    5


#define NO         "no"
#define LSOLVE     "lsolve"
#define COMBINE    "combine"
#define PROJECTION "projection"
#define TRANSFER   "transfer"
#define TOTAL      "totaltime"


struct TestName
{
  TestName(std::size_t system_size_, std::size_t no_atoms_,
	   std::size_t no_interface_, std::size_t no_bridge_rules_)
    : system_size(system_size_), no_atoms(no_atoms_),
      no_interface(no_interface_), no_bridge_rules(no_bridge_rules_)
  { }

  std::size_t system_size;
  std::size_t no_atoms;
  std::size_t no_interface;
  std::size_t no_bridge_rules;

  bool 
  operator==(const TestName& other) const
  {
    return (system_size == other.system_size && no_atoms == other.no_atoms &&
	    no_interface == other.no_interface && no_bridge_rules == other.no_bridge_rules);
  }

  bool
  operator<(const TestName& other) const
  {
    if (system_size < other.system_size)
      {
	return true;
      }
    else if (system_size == other.system_size)
      {
	if (no_atoms < other.no_atoms)
	  {
	    return true;
	  }
	else if (no_atoms == other.no_atoms)
	  {
	    if (no_interface < other.no_interface)
	      {
		return true;
	      }
	    else if (no_interface == other.no_interface)
	      {
		if (no_bridge_rules < other.no_bridge_rules)
		  {
		    return true;
		  }
	      }
	  }
      }
    return false;
  }
};


std::ostream&
operator<< (std::ostream& os, const TestName& testname)
{
  os << testname.system_size << "-" << testname.no_atoms << "-" 
     << testname.no_interface << "-" << testname.no_bridge_rules;

  return os;
}


typedef std::vector<std::size_t> IntVec;
typedef boost::shared_ptr<IntVec> IntVecPtr;
typedef std::vector<float> FloatVec;
typedef boost::shared_ptr<FloatVec> FloatVecPtr;
typedef std::vector<std::string> StrVec;
typedef boost::shared_ptr<StrVec> StrVecPtr;

struct Info
{
  Info()
    : no_answers(new StrVec), lsolve(new StrVec),
      projection(new StrVec), combine(new StrVec),
      transfer(new StrVec), total(new StrVec)
  { }

  StrVecPtr no_answers;
  StrVecPtr lsolve;
  StrVecPtr projection;
  StrVecPtr combine;
  StrVecPtr transfer;
  StrVecPtr total;
};

typedef boost::shared_ptr<Info> InfoPtr;
typedef std::pair<TestName, InfoPtr> InfoPair;
typedef std::map<TestName, InfoPtr> InfoMap;
typedef boost::shared_ptr<InfoMap> InfoMapPtr;

InfoMapPtr diamond(new InfoMap);
InfoMapPtr zigzag(new InfoMap);
InfoMapPtr house(new InfoMap);
InfoMapPtr ring(new InfoMap);
InfoMapPtr tree(new InfoMap);
InfoMapPtr diamond_opt(new InfoMap);
InfoMapPtr zigzag_opt(new InfoMap);
InfoMapPtr house_opt(new InfoMap);
InfoMapPtr ring_opt(new InfoMap);
InfoMapPtr tree_opt(new InfoMap);
InfoMapPtr working;


void
update_vector(StrVecPtr vec, const std::string& filename)
{
  std::ifstream file_data(filename.c_str());
  std::string line;

  while (!file_data.eof())
    {
      getline(file_data, line);
      if (line.compare("") != 0)
	{
	  vec->push_back(line);
	}
    }
}


void process_file(const std::string& filename)
{
  std::size_t dat_signal = filename.find(DAT);
  if (dat_signal != std::string::npos)
    {
      std::string str_info = filename.substr(0, filename.length() - 4);
      typedef boost::tokenizer<boost::char_separator<char> > Tokenizer;
      boost::char_separator<char> sep("-");
      Tokenizer tokens(str_info, sep);
      
      Tokenizer::const_iterator it = tokens.begin();
      int record_topology;

      if (it->compare(DIAMOND) == 0)
	{
	  record_topology = DIAMOND_TOPO;
	}
      else if (it->compare(ZIGZAG) == 0)
	{
	  ++it;
	  record_topology = ZIGZAG_TOPO;
	}
      else if (it->compare(HOUSE) == 0)
	{
	  record_topology = HOUSE_TOPO;
	}
      else if (it->compare(RING) == 0)
	{
	  record_topology = RING_TOPO;
	}
      else if (it->compare(TREE) == 0)
	{
	  record_topology = TREE_TOPO;
	}

      std::size_t system_size;
      std::size_t no_atoms;
      std::size_t no_interface;
      std::size_t no_bridge_rules;

      ++it; system_size = std::atoi(it->c_str());
      ++it; no_atoms = std::atoi(it->c_str());
      ++it; no_interface = std::atoi(it->c_str());
      ++it; no_bridge_rules = std::atoi(it->c_str());
      TestName testname(system_size, no_atoms, no_interface, no_bridge_rules);

      ++it;
      if (it->compare(DMCSOPT) == 0)
	{
	  record_topology = -record_topology;
	}

      switch (record_topology)
	{
	case DIAMOND_TOPO:
	  working = diamond;
	  break;
	case ZIGZAG_TOPO:
	  working = zigzag;
	  break;
	case HOUSE_TOPO:
	  working = house;
	  break;
	case RING_TOPO:
	  working = ring;
	  break;
	case TREE_TOPO:
	  working = tree;
	  break;
	case -DIAMOND_TOPO:
	  working = diamond_opt;
	  break;
	case -ZIGZAG_TOPO:
	  working = zigzag_opt;
	  break;
	case -HOUSE_TOPO:
	  working = house_opt;
	  break;
	case -RING_TOPO:
	  working = ring_opt;
	  break;
	case -TREE_TOPO:
	  working = tree_opt;
	  break;
	}

      InfoMap::iterator jt = working->find(testname);

      if (jt == working->end())
	{
	  InfoPtr info(new Info);
	  InfoPair info_pair(testname, info);

	  std::pair<InfoMap::iterator, bool> ret;
	  ret = working->insert(info_pair);

	  assert ( ret.second );

	  jt = ret.first;
	}

      InfoPtr info = jt->second;
      ++it;
      if (it->compare(NO) == 0)
	{
	  update_vector(info->no_answers, filename);
	}
      else if (it->compare(LSOLVE) == 0)
	{
	  update_vector(info->lsolve, filename);
	}
      else if (it->compare(COMBINE) == 0)
	{
	  update_vector(info->combine, filename);
	}
      else if (it->compare(PROJECTION) == 0)
	{
	  update_vector(info->projection, filename);
	}
      else if (it->compare(TRANSFER) == 0)
	{
	  update_vector(info->transfer, filename);
	}
      else if (it->compare(TOTAL) == 0)
	{
	  update_vector(info->total, filename);
	}
    }
}


void
print_table(InfoMapPtr topo, InfoMapPtr topo_opt)
{
  for (InfoMap::const_iterator it = topo->begin(); it != topo->end(); ++it)
    {
      std::cerr << it->first << std::endl;
      InfoPtr info = it->second;

      InfoMap::const_iterator jt = topo_opt->find(it->first);
      
      // must also have test result for the optimal case
      assert (jt != diamond_opt->end());
      InfoPtr info_opt = jt->second;

      assert (info->no_answers->size() == info->lsolve->size() &&
	      info->lsolve->size() == info->combine->size() &&
	      info->combine->size() == info->projection->size() &&
	      info->projection->size() == info->transfer->size() &&
	      info->transfer->size() == info->total->size());

      assert (info_opt->no_answers->size() == info_opt->lsolve->size() &&
	      info_opt->lsolve->size() == info_opt->combine->size() &&
	      info_opt->combine->size() == info_opt->projection->size() &&
	      info_opt->projection->size() == info_opt->transfer->size() &&
	      info_opt->transfer->size() == info_opt->total->size());

      StrVec::const_iterator it_no_answers = info->no_answers->begin();
      StrVec::const_iterator it_lsolve     = info->lsolve->begin();
      StrVec::const_iterator it_combine    = info->combine->begin();
      StrVec::const_iterator it_projection = info->projection->begin();
      StrVec::const_iterator it_transfer   = info->transfer->begin();
      StrVec::const_iterator it_total      = info->total->begin();

      StrVec::const_iterator it_no_answers_opt = info_opt->no_answers->begin();
      StrVec::const_iterator it_lsolve_opt     = info_opt->lsolve->begin();
      StrVec::const_iterator it_combine_opt    = info_opt->combine->begin();
      StrVec::const_iterator it_projection_opt = info_opt->projection->begin();
      StrVec::const_iterator it_transfer_opt   = info_opt->transfer->begin();
      StrVec::const_iterator it_total_opt      = info_opt->total->begin();

      for (; it_no_answers_opt != info_opt->no_answers->end(); 
	   ++it_no_answers_opt, ++it_lsolve_opt,
	     ++it_combine_opt, ++it_projection_opt,
	     ++it_transfer_opt, ++it_total_opt)
	{
	  if (it_no_answers != info->no_answers->end())
	    {
	      std::cerr << *it_no_answers << " " << *it_lsolve << " " << *it_combine << " " 
			<< *it_projection << " " << *it_transfer << " " << *it_total << "-"
			<< *it_no_answers_opt << " " << *it_lsolve_opt << " " << *it_combine_opt << " " 
			<< *it_projection_opt << " " << *it_transfer_opt << " " << *it_total_opt << "!" << std::endl;

	      ++it_no_answers;
	      ++it_lsolve;
	      ++it_combine;
	      ++it_projection;
	      ++it_transfer;
	      ++it_total;
	    }
	  else 
	    {
	      std::cerr << *it_no_answers_opt << " " << *it_lsolve_opt << " " << *it_combine_opt << " " 
			<< *it_projection_opt << " " << *it_transfer_opt << " " << *it_total_opt << "!" << std::endl;
	    }
	}
    }
}



void
print_table(std::stringstream& out, const char *topo_name, InfoMapPtr topo, InfoMapPtr topo_opt)
{
  out << "<table border=1>\n<thead>\n<tr>" << std::endl;
  out << "<th></th>" << std::endl;
  out << "<th colspan=\"6\">DMCS</th>" << std::endl;
  out << "<th colspan=\"6\">DMCSOPT</th>\n</tr>" << std::endl;
  out << "<tr><th>Test</th>"
      << "<th>#answers</th><th>lsolve</th><th>projection</th><th>combine</th><th>transfer</th><th>total</th>"
      << "<th>#answers</th><th>lsolve</th><th>projection</th><th>combine</th><th>transfer</th><th>total</th></tr>" << std::endl
      << "</thead>\n<tbody>" << std::endl;

  for (InfoMap::const_iterator it = topo->begin(); it != topo->end(); ++it)
    {
      InfoMap::const_iterator jt = topo_opt->find(it->first);
      
      // must also have test result for the optimal case
      assert (jt != diamond_opt->end());

      InfoPtr info = it->second;
      InfoPtr info_opt = jt->second;

      assert (info->no_answers->size() == info->lsolve->size() &&
	      info->lsolve->size() == info->combine->size() &&
	      info->combine->size() == info->projection->size() &&
	      info->projection->size() == info->transfer->size() &&
	      info->transfer->size() == info->total->size());

      assert (info_opt->no_answers->size() == info_opt->lsolve->size() &&
	      info_opt->lsolve->size() == info_opt->combine->size() &&
	      info_opt->combine->size() == info_opt->projection->size() &&
	      info_opt->projection->size() == info_opt->transfer->size() &&
	      info_opt->transfer->size() == info_opt->total->size());

      out << "<tr class=\"odd\">" << std::endl;
      out << "<th rowspan=\"" << info_opt->no_answers->size() << "\"> " 
	  << "<a href=\"testcases/" << topo_name << "-" << it->first << ".zip\">" 
	  << it->first << "</a></th>" << std::endl;


      StrVec::const_iterator it_no_answers = info->no_answers->begin();
      StrVec::const_iterator it_lsolve     = info->lsolve->begin();
      StrVec::const_iterator it_combine    = info->combine->begin();
      StrVec::const_iterator it_projection = info->projection->begin();
      StrVec::const_iterator it_transfer   = info->transfer->begin();
      StrVec::const_iterator it_total      = info->total->begin();

      StrVec::const_iterator it_no_answers_opt = info_opt->no_answers->begin();
      StrVec::const_iterator it_lsolve_opt     = info_opt->lsolve->begin();
      StrVec::const_iterator it_combine_opt    = info_opt->combine->begin();
      StrVec::const_iterator it_projection_opt = info_opt->projection->begin();
      StrVec::const_iterator it_transfer_opt   = info_opt->transfer->begin();
      StrVec::const_iterator it_total_opt      = info_opt->total->begin();

      std::size_t count = 1;
      for (; it_no_answers_opt != info_opt->no_answers->end(); 
	   ++it_no_answers_opt, ++it_lsolve_opt,
	     ++it_combine_opt, ++it_projection_opt,
	     ++it_transfer_opt, ++it_total_opt,
	     ++count)
	{
	  if (it_no_answers_opt != info_opt->no_answers->begin())
	    {
	      if (count % 2)
		{
		  out << "<tr class=\"odd\">" << std::endl;
		}
	      else
		{
		  out << "<tr>" << std::endl;
		}
	    }

	  if (it_no_answers != info->no_answers->end())
	    {
	      out << "<td>" << *it_no_answers << "</td><td>" << *it_lsolve << "</td><td>" << *it_projection << "</td><td>" 
			<< *it_combine << "</td><td>" << *it_transfer << "</td><td>" << *it_total << "</td><td>"
			<< *it_no_answers_opt << "</td><td>" << *it_lsolve_opt << "</td><td>" << *it_projection_opt << "</td><td>" 
			<< *it_combine_opt << "</td><td>" << *it_transfer_opt << "</td><td>" << *it_total_opt << "</td>" << std::endl;

	      ++it_no_answers;
	      ++it_lsolve;
	      ++it_combine;
	      ++it_projection;
	      ++it_transfer;
	      ++it_total;
	    }
	  else 
	    {
	      out << "<td>&mdash;</td><td>&mdash;</td><td>&mdash;</td><td>&mdash;</td><td>&mdash;</td><td>&mdash;</td><td>" 
		  << *it_no_answers_opt << "</td><td>" << *it_lsolve_opt << "</td><td>" << *it_projection_opt << "</td><td>" 
		  << *it_combine_opt << "</td><td>" << *it_transfer_opt << "</td><td>" << *it_total_opt << "</td>" 
		  << std::endl << "</tr>" << std::endl;
	    }
	}
    }

  out << "</tbody>\n</table>" << std::endl;
}


int main()
{
  DIR *dir;
  struct dirent *ent;
  dir = opendir ("/Users/minhdt/Documents/Study/mhex/dmcsopt-jelia/test-result/unordered-timings");

  if (dir != NULL) {
    // Look into all file names, find the test cases for extracting information
    while ((ent = readdir (dir)) != NULL) {
      std::string filename = ent->d_name;
      //std::cerr << "Processing: " << filename << std::endl;
      process_file(filename);
    }
    closedir (dir);
  }

  std::stringstream out;
  out << "<p><h4>Diamond</h4></p>" << std::endl;
  print_table(out, "diamond", diamond, diamond_opt);

  out << "<p><h4>Ring</h4></p>" << std::endl;
  print_table(out, "ring", ring, ring_opt);

  out << "<p><h4>Zig-zag</h4></p>" << std::endl;
  print_table(out, "zig-zag", zigzag, zigzag_opt);

  out << "<p><h4>House</h4></p>" << std::endl;
  print_table(out, "house", house, house_opt);

  out << "<p><h4>Tree</h4></p>" << std::endl;
  print_table(out, "tree", tree, tree_opt);

  std::ofstream file_table;
  file_table.open("table.txt");
  file_table << out.str() << std::endl;
  file_table.close();
}
